/* Copyright (C) 2009, 2011 Christofer Jonsson
 *
 * This file is part of FXH.
 *
 * FXH is free software: you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * FXH is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
 * License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with FXH.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "fxh_pbar.h"
#include "fxh_util.h"
#include "fxh_error.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>
#include <time.h>

#define MAX_FILENAME_SIZE (32)

static const double kilobyte = 1024.0;
static const double megabyte = 1048576.0;
static const double gigabyte = 1073741824.0;
static const double terabyte = 1099511627776.0;

struct 
{
   char filename[MAX_FILENAME_SIZE];
   uint64_t filesize;
   uint64_t transmitted;

   struct
   {
      pthread_t updater;
      pthread_mutex_t mutex;
      fxh_bool_t updater_alive;

      struct
      {
	 size_t bytes;
	 double time;
      } curr;

      struct
      {
	 size_t bytes;
	 double time;	 
      } calc;

   } speed;
   
} pbar;

static int calc_percent(uint64_t transmitted, uint64_t filesize);
static void* pbar_speed_updater(void *dummy);
static void print_percent(int percent);
static void print_pbar(int percent);
static void print_filesize(uint64_t filesize);
static void print_filename(const char *filename);
static void print_speed(size_t bytes, double time);

static void pbar_upd_speed(size_t last_trans_bytes,
			   double last_trans_time);

static void pbar_get_speed(size_t *bytes,
			   double *time);

void fxh_pbar_init(const char *filename, uint64_t filesize)
{
   pthread_attr_t attr;
   int res = 0;
   size_t filename_size = strlen(filename) + 1;

   if (filename_size > MAX_FILENAME_SIZE) 
   {
      strncpy(pbar.filename, filename, MAX_FILENAME_SIZE - 4);
      strncpy(pbar.filename + MAX_FILENAME_SIZE - 4, "...", 4); 
   }
   else
   {
      strncpy(pbar.filename, filename, MAX_FILENAME_SIZE);
   }

   pbar.filesize = filesize;

   /* Set default values */
   pbar.transmitted = 0;
   pbar.speed.curr.bytes = 0;
   pbar.speed.curr.time = 0.0;
   pbar.speed.calc.bytes = 0;
   pbar.speed.calc.time = 0.0;

   /* Speed updater thread setup */
   pbar.speed.updater_alive = FXH_TRUE;
   pthread_mutex_init(&pbar.speed.mutex, NULL);
   pthread_attr_init(&attr);
   pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
   res = pthread_create(&pbar.speed.updater, &attr, pbar_speed_updater, NULL);

   if (0 != res)
   {
      FXH_ERROR("Call to pthread_create failed with error code: %d", res);
      exit(EXIT_FAILURE);
   }
}

void fxh_pbar_finish()
{
   int percent = 0;

   /* Stop thread */
   pbar.speed.updater_alive = FXH_FALSE;
   pthread_join(pbar.speed.updater, NULL);   
   pthread_mutex_destroy(&pbar.speed.mutex);

#ifndef TEST
   /* Clear console line */
   printf("\033[2K");
#endif

   percent = calc_percent(pbar.transmitted, pbar.filesize);
   print_percent(percent);
   print_pbar(percent);
   print_filesize(pbar.filesize);
   print_filename(pbar.filename);

   printf("\n");
   fflush(stdout);
}  

void fxh_pbar_update(uint64_t transmitted,
		     size_t last_trans_bytes,
		     double last_trans_time)
{
   int percent = 0;
   size_t bytes = 0;
   double time = 0.0; 

   pbar_upd_speed(last_trans_bytes, 
		  last_trans_time);

   pbar_get_speed(&bytes, &time);

   pbar.transmitted = transmitted;
   percent = calc_percent(pbar.transmitted, pbar.filesize);
   print_percent(percent);
   print_pbar(percent);
   print_speed(bytes, time);
   print_filesize(pbar.filesize);
   print_filename(pbar.filename);

#ifdef TEST
   printf("\n");
#else
   printf("\r");
#endif

   fflush(stdout);
}

static void pbar_upd_speed(size_t last_trans_bytes,
			   double last_trans_time)
{
   pthread_mutex_lock(&pbar.speed.mutex);

   pbar.speed.calc.bytes += last_trans_bytes;
   pbar.speed.calc.time += last_trans_time;

   pthread_mutex_unlock(&pbar.speed.mutex);
}

static void pbar_get_speed(size_t *bytes,
			   double *time)
{
   pthread_mutex_lock(&pbar.speed.mutex);

   *bytes = pbar.speed.curr.bytes;
   *time = pbar.speed.curr.time;

   pthread_mutex_unlock(&pbar.speed.mutex);
}

static void print_percent(int percent)
{
   printf(" [%3d%%]", percent);
}

static void print_pbar(int percent)
{
   static char pbar[21];
   int i = 0, j= 0;

   while (i < 20)
   {
      pbar[i++] = (percent > j) ? '=' : ' ';
      j += 5;
   }

   pbar[20] = '\0';
   printf(" |%s|", pbar);
}

static void print_speed(size_t bytes, double time)
{
   double bytes_per_sec = 0.0;
   double res = 0.0;

   if (0 == bytes)
   {
      printf(" [   0 B/s ]");
      return;
   }

   bytes_per_sec = (((double)bytes) / time) * 1000.0;
      
   if ((res = bytes_per_sec / terabyte) > 1.0)
   {
      printf(" [%4.0f TB/s]", res);
   }
   else if ((res = bytes_per_sec / gigabyte) > 1.0)
   {
      printf(" [%4.0f GB/s]", res);
   }
   else if ((res = bytes_per_sec / megabyte) > 1.0)
   {
      printf(" [%4.0f MB/s]", res);
   }
   else if ((res = bytes_per_sec / kilobyte) > 1.0)
   {
      printf(" [%4.0f kB/s]", res);
   }
   else
   {
      printf(" [%4.0f B/s ]", bytes_per_sec);
   }   
}

static void print_filesize(uint64_t filesize)
{
   double res = 0.0;

   if ((res = (double)filesize / terabyte) > 1.0)
   {
      printf(" [%4.0f TB]", res);
   }
   else if ((res = (double)filesize / gigabyte) > 1.0)
   {
      printf(" [%4.0f GB]", res);
   }
   else if ((res = (double)filesize / megabyte) > 1.0)
   {
      printf(" [%4.0f MB]", res);
   }
   else if ((res = (double)filesize / kilobyte) > 1.0)
   {
      printf(" [%4.0f kB]", res);
   }
   else
   {
      printf(" [%4d B ]", (int)filesize);
   }   
}

static void print_filename(const char *filename)
{
   printf(" [%s]", filename);
}

static int calc_percent(uint64_t transmitted, uint64_t filesize)
{
   if (filesize == 0)
      return 100;
   else
      return (int)(((double)transmitted / (double)filesize) * 100);
}

static void* pbar_speed_updater(void *dummy)
{
   struct timespec sleep_time;
   int i;
   
   (void)dummy;

   /* Sleep time is 100 ms */
   sleep_time.tv_sec = 0;
   sleep_time.tv_nsec = 100000000;

   for (;;)
   {
      for (i = 0; i < 10; ++i)
      {
	 if (FXH_FALSE == pbar.speed.updater_alive)
	    goto end;

	 nanosleep(&sleep_time, NULL);
      }

      /* Progress bar updated with new data every 1000 ms (1 s) */
      pthread_mutex_lock(&pbar.speed.mutex);
	 
      pbar.speed.curr.bytes = pbar.speed.calc.bytes;
      pbar.speed.curr.time = pbar.speed.calc.time;
      pbar.speed.calc.bytes = 0;      
      pbar.speed.calc.time = 0.0;
      
      pthread_mutex_unlock(&pbar.speed.mutex);      
   }

  end:
   
   pthread_exit((void*)0);
}
